Rust実装でPiping Severの転送速度を1.7倍~1.8倍高速化 (Hyper)
https://gh-card.dev/repos/nwtgck/piping-server-rust.svg https://github.com/nwtgck/piping-server-rust
Rustで実装する理由
Node.js(TypeScript) vs Rustの転送速度の比較
単純な転送速度の比較。
https://youtu.be/KRe_5L6YijE
目視でわかる速さの向上でRustの凄さがわかる。メモリ効率など調べれば他の点でも良いところが分かるかもしれない。 サーバーの動かし方
2種類の方法で立て方を紹介。
Dockerですぐに動かす。localhost:8181にサーバーが立つ。 code:bash
docker run -p 8181:8080 --init nwtgck/piping-server-rust
ソースから動かす方法。--releaseは最適化目的。
code:bash
cargo run --release
オリジナルPiping Serverとの違い
基本的には同じ。パスを指定して、POST/PUTで送信、GETで受信。
ただ、多少の違いがある。
技術的な話
HyperをHTTPのサーバーのライブラリとして使っている 調べた感じだと、HyperはRustのデファクトスタンダードなHTTPサーバーライブラリの様子
HTTP/2に対応している
unsafeは使っていない
追記
レスポンスを待たせたり、ストリーミングすることへのこだわり
Piping Serverは割とシンプルな仕組みで動いている。だが、HTTPサーバーがよく使われるREST APIサーバーや静的なホスティングとも異なっている。特に送信者がいなければ待ったり、受信者がいなければ待ったり、送信者のHTTPボディを受信者に流し込んだりする。こういう面がよくあるHTTPサーバーの使われ方と異なっている。そのため、Piping Serverで必要な技術をRustとHyperで実現するためにいくつかプロトタイプを作りながら試していった。
「送信者がいなければ待ったり、受信者がいなければ待ったり」の部分は、チャンネルを使っている。具体的にはfutures::sync::oneshot::Receiver<>を使っている。現在のHyperの0.12だと、service_fn()の引数は、Future<Item=Response<Body>...のようなものを返す、以下のコードのようにReceiverがFutureをimplしているため、ちょうどfutures::sync::oneshot::Receiver<Response<Body>>をservice_fn()の引数に渡る関数の戻り値にすることで待たせることが可能になった。
code:rs
// futures-0.1.27/src/sync/oneshot.rs
impl<T> Future for Receiver<T> {
type Item = T;
type Error = Canceled;
fn poll(&mut self) -> Poll<T, Canceled> {
self.inner.recv()
}
}
このチャンネルの使い方は、JavaScriptとかのPromiseをresolveする感覚でReceiverに対応するSenderからBodyを送りこむイメージで使っている。
ハンドラーのAPI - req => res VS (req, res) => void
ハンドラーのAPIとしてレスポンスを戻り値として返すのは、低いレベルでHTTPサーバーを書くときは使いづらいのかなって思っている。つまり、service_fn()の引数が細かいことを無視するとreq: Request<Body> -> Future<Response<Body>>になっているのが扱いづらいと思っている。
Node.jsやGo言語のHTTPサーバーを書くときは、ハンドラーは(req, res) => voidみたいな型になる。イメージ的にはレスポンスは返すものであるが、リクエストとレスポンス同時に操作しやすい利点がある。あと元々ソケットが双方向通信可能な存在であるのでreq, resがハンドラー内で相互に操作できる方が低いレベルのHTTPサーバーが書きやすいのかなと思っている
現在のところオリジナルを置き換えるつもりなどはなく、TypeScriptのオリジナルと並行して存在する感じ。
まだHyperも1.xでもないし、変化もありそう。少なくともクライアント接続切れの検知ができる実装などができるようにしたい。この高速化はすごく有益なので、リバースプロキシで、例えばppng.ml/fast/mypath1とかppng.ml/fast/mydataだとRust版を使うようにするなどの実験的な試みはしても良いと思っている。